﻿using System;

namespace InDepth2Demo.Chapter9
{
    /// <summary>
    /// 9.4 类型推断和重载决策的改变
    /// </summary>
    public class TypeInference
    {
        // 需要新的类型推断规则的例子
        static void PrintConvertedValue<TInput, TOutput>(TInput input, Converter<TInput, TOutput> converter)
        {
            Console.WriteLine(converter(input));
        }

        static void Call()
        {
            PrintConvertedValue("I'm a string", x => x.Length);
        }

        //代码清单9-12 尝试推断匿名方法的返回类型
        delegate T MyFunc<T>();          // 声明了 .NET2.0 中没有的 Func<T>

        static void WriteResult<T>(MyFunc<T> function)          // 声明带有委托参数的泛型方法
        {
            Console.WriteLine(function());
        }

        public static void Call2()
        {
            WriteResult(delegate { return 5; });           // 要求对 T 进行类型推断
        }
        // 这段代码在C# 2中编译会报错：
        // error CS0411:The type arguments for method 'Snippet.WriteResult<T>(Snippet.MyFunc<T>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
        // 无法从用法中推断出 'WriteResult<T>（Snippet.MyFunc<T>）'。尝试显式指定类型参数。

        // 可以采取两种方式修正这个错误：要么显式指定类型实参(就像编译器推荐的那样)，要么将匿名方法强制转换为一个具体的委托类型:
        public static void Call3()
        {
            WriteResult<int>(delegate { return 5; });
            WriteResult((MyFunc<int>)delegate { return 5; });
        }
        // 上面也太麻烦了。我们希望编译器能像对非委托类型所做的那样，执行相同的类型推断，也就是根据返回的表达式的类型来推断 T 的类型。


        // 根据一天当中的时间来选择返回 int 或 object
        public static void Call4()
        {
            WriteResult(delegate
            {
                if (DateTime.Now.Hour < 12)
                {
                    return 10;                  // 返回类型是 int
                }
                else
                {
                    return new object();           // 返回类型是 object
                }
            });
        }
        //在这种情况下，编译器采用和处理隐式类型的数组时相同的逻辑来确定返回类型。
        //它构造一个集合，其中包含了来自匿名函数主体中的 return 语句的所有类型(本例是int和object)，并检查其中是否只有一个类型能由其他所有类型隐式转换而来，并能转换成其他类型。
        //int到object存在一个隐式转换(通过装箱)，但object到int就不存在了。所以，object被推断为返回类型。
        //如果没有找到符合条件的类型，或者找到了多个，就无法推断出返回类型，编译器会报错。

        //我们现在知道了怎样确定匿名函数的返回类型--但是，参数类型可以隐式定义的 Lambda 表达式又如何呢?


        //9.4.3 分两个阶段进行的类型推断
        // 综合来自多个实参的信息，灵活地进行推断
        static void PrintType<T>(T first, T second)
        {
            Console.WriteLine(typeof(T));
        }
        static void Call5()
        {
            PrintType(1, new object());
        }
        //C# 2 编译器：第一个实参判断出T是int，第二个判断出T是object。两者冲突了。
        //C# 3 编译器：构造集合，包含里面有的所有类型，且判断是否只有一个类型能由其他类型隐式转换而来，比如 int 到 object 就存在隐式转换。所以，判断出 T 应该是 object。


        // 多级类型推断
        static void ConvertTwice<TInput, TMiddle, TOutput>(TInput input,
                                                            Converter<TInput, TMiddle> firstConversion,
                                                            Converter<TMiddle, TOutput> secondConversion)
        {
            TMiddle middle = firstConversion(input);
            TOutput output = secondConversion(middle);
            Console.WriteLine(output);
        }
        static void Call6()
        {
            ConvertTwice("Another string", text => text.Length, length => Math.Sqrt(length));
        }
        //类型推断的“阶段1”告诉编译器肯定存在从 string 到 TInput 的一个转换。
        //第一次执行“阶段2”时，IInput 固定为 string，我们推断肯定存在从 int 到 TMiddle 的一个转换。
        //第二次执行“阶段2”时，TMiddle 固定为 int，我们推断肯定存在从 double 到 Toutput 的一个转换。
        //第三次执行“阶段2”时，Toutput 固定为 double，类型推断成功。当类型推断结束后，编译器就可以正确地理解 Lambda 表达式中的代码。


        static void Write(int x, double y) { }
        static void Write(double y, int x) { }
        static void Call7()
        {
            // Write(1, 1);  //错误 CS0121  以下方法或属性之间的调用具有二义性:“TypeInference.Write(int, double)”和“TypeInference.Write(double, int)”	
        }

        // 委托返回类型影响了重载选择
        static void Execute(Func<int> action)
        {
            Console.WriteLine("action returns an int:" + action());
        }
        static void Execute(Func<double> action)
        {
            Console.WriteLine("action returns an double:" + action());
        }
        static void Call8()
        {
            Execute(() => 1);
        }
        //现在是从一个无参数、推断返回类型为int的Lambda表达式转换成 Func<int> 或 Func<double>。 两个委托类型的参数列表是相同的(空)，所以上述规则是适用的。
        //然后，我们只需判断哪个转换“更好”就可以了:int到int，还是int到double。
        //这样就回到了我们熟悉的问题上--如前所述，int到int的转换更好。因此，代码清单9-16会在屏幕上显示: action returns an int:1。

    }
}
